package com.easy.framework.security;

import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.easy.framework.common.config.SecretProperties;
import com.easy.framework.common.constant.Constants;
import com.easy.framework.common.constant.I18NConstants;
import com.easy.framework.common.utils.I18NUtils;
import com.easy.framework.common.utils.Sm2Utils;
import com.easy.framework.common.utils.ip.IpUtils;
import com.easy.framework.security.service.ILoginLogService;
import com.easy.framework.security.service.IUserDetailsService;
import com.easy.framework.security.utils.SecurityUtils;
import com.easy.framework.security.utils.WebUtils;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.core.Ordered;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.web.authentication.www.BasicAuthenticationConverter;
import org.springframework.util.Assert;

import java.util.Comparator;
import java.util.Map;
import java.util.Optional;

public class MyAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
    private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
    private PasswordEncoder passwordEncoder;
    private volatile String userNotFoundEncodedPassword;
    private UserDetailsPasswordService userDetailsPasswordService;
    private ILoginLogService loginLogService;
    private SecretProperties secretProperties;

    private final static BasicAuthenticationConverter basicConvert = new BasicAuthenticationConverter();

    public MyAuthenticationProvider() {
        passwordEncoder = SecurityUtils.getPasswordEncoder();
        loginLogService = SpringUtil.getBean(ILoginLogService.class);
        secretProperties = SecretProperties.build();
    }

    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {

        boolean isSuccess = false;
        HttpServletRequest request = WebUtils.getRequest().get();
        String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
        String userAgent = request.getHeader("User-Agent");
        String ip = IpUtils.getIpAddr(request);
        try {
            if (authentication.getCredentials() != null) {
                String password = authentication.getCredentials().toString();
                if (AuthorizationGrantType.PASSWORD.getValue().equals(grantType)) {
                    password = Sm2Utils.decrypt(secretProperties.getPrivateKey(), password, secretProperties.getCharset());
                }
                isSuccess = this.passwordEncoder.matches(password, userDetails.getPassword());
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (!isSuccess) {
                loginLogService.addAsync(userDetails.getUsername(), grantType, isSuccess,userAgent,ip, I18NUtils.message(I18NConstants.AUTHENTICATION_BAD_CREDENTIALS));
                this.logger.debug("Failed to authenticate since password does not match stored value");
                String msg = this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials");
                throw new BadCredentialsException(msg);
            }else{
                loginLogService.addAsync(userDetails.getUsername(), grantType, isSuccess,userAgent,ip, "登录成功");
            }
        }
    }

    @Override
    protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        this.prepareTimingAttackProtection();
        Map<String, IUserDetailsService> userDetailsServiceMap = SpringUtil
                .getBeansOfType(IUserDetailsService.class);
        HttpServletRequest request = WebUtils.getRequest().get();
        String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
        String clientId = request.getParameter(OAuth2ParameterNames.CLIENT_ID);
        if (StrUtil.isBlank(clientId)) {
            UsernamePasswordAuthenticationToken upat = basicConvert.convert(request);
            if (upat != null) {
                clientId = upat.getName();
            }
        }
//        if (!SecurityConstants.PASSWORD.equals(grantType) && !SecurityConstants.MOBILE.equals(grantType)) {
//            String captcha = request.getParameter(SecurityConstants.CAPTCHA);
//            String captchaId = request.getParameter(SecurityConstants.CAPTCHA_ID);
//            String code = redisService.getCacheObject(CacheConstants.CAPTCHA_CODE_KEY + captchaId);
//            if (Func.isEmpty(code)) {
//                throw new InternalAuthenticationServiceException("验证码已过期！");
//            }
//            if (!code.equalsIgnoreCase(captcha)) {
//                throw new InternalAuthenticationServiceException("验证码不正确！");
//            }
//            redisService.deleteObject(CacheConstants.CAPTCHA_CODE_KEY + captchaId);
//        }
        String finalClientId = clientId;
        String finalGrantType = grantType;
        Optional<IUserDetailsService> optional = userDetailsServiceMap.values()
                .stream()
                .filter(service -> service.support(finalClientId, finalGrantType))
                .max(Comparator.comparingInt(Ordered::getOrder));
        try {
            UserDetails loadedUser = optional.get().loadUserByUsername(username);
            if (loadedUser == null) {
                throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
            } else {
                return loadedUser;
            }
        } catch (UsernameNotFoundException var4) {
            this.mitigateAgainstTimingAttack(authentication);
            throw var4;
        } catch (InternalAuthenticationServiceException var5) {
            throw var5;
        } catch (Exception var6) {
            var6.printStackTrace();
            throw new InternalAuthenticationServiceException(var6.getMessage(), var6);
        }
    }

    @Override
    protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
        boolean upgradeEncoding = this.userDetailsPasswordService != null && this.passwordEncoder.upgradeEncoding(user.getPassword());
        if (upgradeEncoding) {
            String presentedPassword = authentication.getCredentials().toString();
            String newPassword = this.passwordEncoder.encode(presentedPassword);
            user = this.userDetailsPasswordService.updatePassword(user, newPassword);
        }
        return super.createSuccessAuthentication(principal, authentication, user);
    }

    private void prepareTimingAttackProtection() {
        if (this.userNotFoundEncodedPassword == null) {
            this.userNotFoundEncodedPassword = this.passwordEncoder.encode(USER_NOT_FOUND_PASSWORD);
        }

    }

    private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {
        if (authentication.getCredentials() != null) {
            String presentedPassword = authentication.getCredentials().toString();
            this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword);
        }

    }

    public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
        Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
        this.passwordEncoder = passwordEncoder;
        this.userNotFoundEncodedPassword = null;
    }

    protected PasswordEncoder getPasswordEncoder() {
        return this.passwordEncoder;
    }

    public void setUserDetailsPasswordService(UserDetailsPasswordService userDetailsPasswordService) {
        this.userDetailsPasswordService = userDetailsPasswordService;
    }

}

